package wellplatescanner;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.TooManyListenersException;

public class ArduinoDevice {
    
	public static interface IDeviceChangedListener {
		void stateChanged(int state);
		void connected();
	}
	
	SerialPort port;
	boolean connected = false;
	OutputStream mOutputToPort;
	InputStream mInputFromPort;
	
	public static final int STATE_SHAKING = 1;
	public static final int STATE_STOPPED = 2;
	
	private int requestedState = STATE_STOPPED; 
	private int state = STATE_STOPPED;
	
    byte[] readBuffer = new byte[128];
    int readBufferIndex = 0;

	IDeviceChangedListener deviceChangedListener;
	
	Thread workerThread;
	
	@Override
	public void finalize() {
		close();
	}
	
	public synchronized int getState() {
		return state;
	}
	
	public synchronized void setRequestedState(int state) {
		
		if(state == this.requestedState) {
			return;
		}
		
		this.requestedState = state;
		
		Log.println("Arduino: requesting state " + getStateName(requestedState));
		
		sendState();
	}

	public synchronized void setDeviceChangedListener(IDeviceChangedListener listener) {
		this.deviceChangedListener = listener;
	}
	
    public synchronized void connect(final CommPortIdentifier portIdentifier)
    {
    	if(port != null && port.getName().equals(portIdentifier.getName())) {
    		return;
    	}
        close();

        // TODO: if already connecting we should wait for that to complete (or communication will be fubar)
        
       workerThread = new Thread(new Runnable() {
			@Override
			public void run() {
		        try {
		        	Log.println("Arduino: connecting to " + portIdentifier.getName() + " .....");
		        	
		        	int timeout = 300;
					port = (SerialPort) portIdentifier.open("WeePlateScanner", timeout);

			    	int baudRate = 9600; //port.getBaudRate();
			    	port.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			    	port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);

			    	port.notifyOnDataAvailable(true);
			    	port.addEventListener(new SerialPortEventListener() {
						@Override
						public void serialEvent(SerialPortEvent arg0) {
							handleSerialPortEvent(arg0);
						}
			    	});
			    	
			    	mOutputToPort = port.getOutputStream();
			    	mInputFromPort = port.getInputStream();
			    	
			    	Thread.sleep(1500);
			    	
			    	// TODO: put this in thread and wait before sending.
			    	// TODO: handshake to confirm arduino code version and state
			    	sendState();

			    	Log.println("Arduino: connected to " + portIdentifier.getName());
			    	
			    	connected = true;
			    	
					if(deviceChangedListener != null) {
						deviceChangedListener.connected();
					}
			    	
		        } catch (PortInUseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					close();
				} catch (UnsupportedCommOperationException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					close();
				} catch (TooManyListenersException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					close();
				} catch (InterruptedException e) {
				}
			}
        });
        workerThread.start();
    }

    public synchronized void close() {
    	connected = false; 
		 if (port != null) {
			 port.removeEventListener();
	         port.close();
	         port = null;
	     }
		 try {
	    	 if(mOutputToPort != null) {
				mOutputToPort.close();
				mOutputToPort = null;
	    	 }
	    	 if(mInputFromPort != null) {
	    		 mInputFromPort.close();
				 mInputFromPort = null;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		if(workerThread != null) {
			workerThread.interrupt();
			workerThread = null;
		}
    }
    
    public synchronized boolean isConnected()
    {
        if (port == null)
            return false;
        return true;
    }

    private void handleSerialPortEvent(SerialPortEvent event) {

    	if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
			try {
				
				int available = mInputFromPort.available();
				mInputFromPort.read(readBuffer, readBufferIndex, available);
				readBufferIndex += available;
				
				// Did we reach end of package?
				if(readBufferIndex > 0) {
					int packageSize = readBuffer[0];
					if(readBufferIndex == packageSize) {
						handleIncommingPackage();
						readBufferIndex = 0;
					}
				}
			} catch (Exception e) {
				System.err.println(e.toString());
			}
		}
     }
    
    private void handleIncommingPackage() {
    	byte size = readBuffer[0];
    	byte data = readBuffer[1];
    	
    	setState(data);
    }
    
    private void setState(int state) {
    	Log.println("Arduino: state is " + getStateName(state));

    	this.state = state;

		if(deviceChangedListener != null) {
			deviceChangedListener.stateChanged(state);
		}
    }
    
    private void sendState() {
		try {
	    	if(mOutputToPort != null) {
	    		
	    		byte[] bytes  = { (byte)2, (byte)requestedState };
	    		mOutputToPort.write(bytes);
				mOutputToPort.flush();
	    	}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
    }
    
    private String getStateName(int s) {
    	switch(s) {
    	case STATE_SHAKING: return "STATE_SHAKING";
    	case STATE_STOPPED: return "STATE_STOPPED";
    	}
    	return "";
    }
}
